﻿using CloudStorageLight.Core;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Auth.OAuth2.Mvc;
using Google.Apis.Auth.OAuth2.Mvc.Filters;
using Google.Apis.Auth.OAuth2.Responses;
using Google.Apis.Auth.OAuth2.Web;
using Google.Apis.Util.Store;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;

namespace CloudStorageLight.Web.Controllers
{
    [ExcludeFromCodeCoverage]
    [AuthorizationCodeActionFilter]
    public class GoogleAuthCallbackController : Controller
    {
        protected Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
        {
            get
            {
                return SessionAppAuthorizationCodeWebApp.CreateAppFlowMetadata(Request.Cookies["GoogleDrivePath"].Value, this.HttpContext);
            }
        }

        /// <summary>Gets the authorization code flow.</summary>
        protected IAuthorizationCodeFlow Flow { get { return FlowData.Flow; } }

        /// <summary>
        /// Gets the user identifier. Potential logic is to use session variables to retrieve that information.
        /// </summary>
        protected string UserId { get { return FlowData.GetUserId(this); } }

        /// <summary>
        /// A callback which gets the error when this controller didn't receive an authorization code. The default 
        /// implementation throws a <seealso cref="TokenResponseException"/>.
        /// </summary>
        protected virtual ActionResult OnTokenError(TokenErrorResponse errorResponse)
        {
            throw new TokenResponseException(errorResponse);
        }

        /// <summary>
        /// The authorization callback which receives an authorization code which contains an error or a code.
        /// If a code is available the method exchange the coed with an access token and redirect back to the original
        /// page which initialized the auth process (using the state parameter).
        /// <para>
        /// The current timeout is set to 10 seconds. You can change the default behavior by setting 
        /// <seealso cref="System.Web.Mvc.AsyncTimeoutAttribute"/> with a different value on your controller.
        /// </para>
        /// </summary>
        /// <param name="authorizationCode">Authorization code response which contains the code or an error.</param>
        /// <param name="taskCancellationToken">Cancellation token to cancel operation.</param>
        /// <returns>
        /// Redirect action to the state parameter or <seealso cref="OnTokenError"/> in case of an error.
        /// </returns>
        [AsyncTimeout(10000)]
        public async virtual Task<ActionResult> IndexAsync(AuthorizationCodeResponseUrl authorizationCode,
            CancellationToken taskCancellationToken)
        {
            if (string.IsNullOrEmpty(authorizationCode.Code))
            {
                var errorResponse = new TokenErrorResponse(authorizationCode);

                return OnTokenError(errorResponse);
            }


            var returnUrl = Request.Url.ToString();
            returnUrl = returnUrl.Substring(0, returnUrl.IndexOf("?"));

            var token = await Flow.ExchangeCodeForTokenAsync(UserId, authorizationCode.Code, returnUrl,
                taskCancellationToken);

            // Extract the right state.
            var oauthState = await AuthWebUtility.ExtracRedirectFromState(Flow.DataStore, UserId, authorizationCode.State);

            return new RedirectResult(oauthState);
        }

    }
}
